package IC.AST;

import IC.AST.nodes.*;
import IC.Parser.sym;

public class CodePrinter implements PropogatingVisitor<Boolean, String> {

	private ASTNode root;

	protected int depth = 0;

	protected static String SPACES = "                                                     ";

	protected static final int ident = 4;

	public CodePrinter(ASTNode symbol) {
		this.root = symbol;
	}

	/**
	 * enter a recursion level - print the header and increment depth
	 * 
	 * @param header
	 *                the object title
	 */
	protected void enter(String header, ASTNode node) {
		System.out.printf("%3d:", node.getLine(), node.getColumn());
		System.out.println(ASTPrinter.SPACES.substring(0, depth * ident) + ""
				+ header);
		depth++;
	}

	protected void enter() {
		depth++;
	}

	/**
	 * exit a recursion level print closing brcket and derement depth
	 */
	protected void leave() {
		depth--;
	}

	protected int getDepth() {
		return this.depth;
	}

	protected void printi(String s) {
		System.out
		.print(ASTPrinter.SPACES.substring(0, depth * ident) + "" + s);
	}

	protected void printi() {
		printi("");
	}

	protected void print(String s) {
		System.out.print(s);
	}

	protected void println() {
		System.out.print("\n");
	}

	protected void append(String s) {
		System.out.print(s);
	}

	public void print() throws Exception {
		depth = 1;
		root.accept(this, false);
	}

	/***********************************************************************
	 * visit functions
	 */

	public String visit(Program that, Boolean isIndent) throws Exception {

		for (ClassDecl cls : that.getClasses()) {
			cls.accept(this, false);
		}

		return null;
	}

	public String visit(ClassDecl that, Boolean isIndent) throws Exception {

		String baseName = (that.getBaseClassID() == null) ? "" : " extends "
			+ that.getBaseClassID();
		String className = that.getClassID();
		append("\n");
		printi("class " + className + baseName + " {\n");
		enter();

		for (ASTNode cmd : that.getClassMembers()) {
			cmd.accept(this, true);
		}
		leave();
		printi("}\n");

		return null;
	}

	public String visit(FieldsDecl that, Boolean isIndent) throws Exception {

		that.getType().accept(this, isIndent);

		StringBuffer s = new StringBuffer(" ");
		String sep = "";
		for (String id : that.getIDs()) {
			s.append(sep + id);
			sep = ", ";
		}
		append(s.toString());
		append(";\n");

		return null;
	}

	public String visit(MethodDecl that, Boolean isIndent) throws Exception {
		if(isIndent) {
			printi();
		}
		if (that.isStatic()) {
			append("static ");
		}

		if (that.getReturnType() != null) {
			that.getReturnType().accept(this, false);
		} else {
			append("void ");
		}

		append(that.getMethodName());
		append("(");
		String sep = "";
		for (FormalDecl fd : that.getFormals()) {
			append(sep);
			fd.accept(this, false);
			sep = ", ";

		}
		append(")");
		if (that.getBody() != null) {
			that.getBody().accept(this, true);
		} else {
			append(";\n");
		}

		return null;
	}

	public String visit(FormalDecl that, Boolean isIndent) throws Exception {

		that.getType().accept(this, isIndent);
		append(that.getID());

		return null;
	}

	/** type decls */
	public String visit(ClassTypeDecl that, Boolean isIndent) throws Exception {
		if(isIndent) {
			printi();
		}
		append(that.getClassID() + " ");
		return null;
	}

	public String visit(ArrayTypeDecl that, Boolean isIndent) throws Exception {
		//if(isIndent) {
		//    printi();
		//}
		that.getElemType().accept(this, isIndent);
		append("[] ");

		return null;
	}

	public String visit(PrimitiveTypeDecl that, Boolean isIndent) throws Exception {
		if(isIndent) {
			printi();
		}

		switch (that.getKind()) {
		case sym.INT:
			append("int ");
			break;
		case sym.BOOLEAN:
			append("boolean ");
			break;
		case sym.STRING:
			append("string ");
			break;
		default:
			append("");
		break;
		}
		return null;
	}

	/** method call */
	public String visit(MethodCallStmt that, Boolean isIndent) throws Exception {

		if (that.getCall() != null) {
			that.getCall().accept(this, isIndent);
		} else {

		}

		return null;
	}

	public String visit(StaticCall that, Boolean isIndent) throws Exception {
		if(isIndent) {
			printi();
		}
		append(that.getClassID() + "." + that.getMethodID() + "(");

		String sep = "";
		for (Expression arg : that.getArgs()) {
			append(sep);
			arg.accept(this, false);
			sep = ", ";
		}
		append(")");
		if(isIndent) {
			append(";\n");
		}
		return null;
	}

	public String visit(VirtualCall that, Boolean isIndent) throws Exception {

		if (that.getInstance() != null) {
			that.getInstance().accept(this, isIndent);
			append(".");
		} else {
			if(isIndent) {
				printi();
			}
		}

		append(that.getMethodID() + "(");
		String sep = "";
		for (Expression arg : that.getArgs()) {
			append(sep);
			arg.accept(this, false);
			sep = ", ";

		}
		append(")");
		if(isIndent) {
			append(";\n");
		}

		return null;
	}

	public String visit(LengthExpr that, Boolean isIndent) throws Exception {

		that.getArray().accept(this, isIndent);
		append(".length");

		return null;
	}

	public String visit(ArrayElmVar that, Boolean isIndent) throws Exception {
		if(isIndent) {
			printi();
		}
		// (new int[5])[2]
		if(that.getArray() instanceof NewArrayExpr) {
			append("(");
			that.getArray().accept(this, false);
			append(")");
		}
		else {
			that.getArray().accept(this, false);
		}

		append("[");
		that.getIndex().accept(this, false);
		append("]");

		return null;
	}

	public String visit(SimpleVar that, Boolean isIndent) throws Exception {
		if(isIndent) {
			printi();
		}
		append(that.getID());
		return null;
	}

	public String visit(MemberVar that, Boolean isIndent) throws Exception {
		if(isIndent) {
			printi();
		}
		// (new ClassA()).f
		if(that.getInstance() instanceof NewClassExpr ||
				that.getInstance() instanceof NewArrayExpr) {
			append("(");
			that.getInstance().accept(this, false);
			append(")");
		} 
		else {
			that.getInstance().accept(this, false);
		}

		append("."+that.getID() + " ");

		return null;
	}

	/** new expr. */

	public String visit(NewArrayExpr that, Boolean isIndent) throws Exception {
		if(isIndent) {
			printi();
		}
		append("new ");
		that.getElemType().accept(this, false);
		append("[");
		that.getSize().accept(this, false);
		append("]");

		return null;
	}

	public String visit(NewClassExpr that, Boolean isIndent) throws Exception {
		if(isIndent) {
			printi();
		}
		append("new " + that.getClassID() + "()");
		return null;
	}



	public String visit(ThisExpr that, Boolean isIndent) throws Exception {
		if(isIndent) {
			printi();
		}
		append("this");
		return null;
	}

	public String visit(BinaryExpr that, Boolean isIndent) throws Exception {
		if(isIndent) {
			printi();
		}
		append("(");
		that.getLeft().accept(this, false);
		append(" " + that.getOp() + " ");
		that.getRight().accept(this, false);
		append(")");
		return null;
	}

	public String visit(UnaryExpr that, Boolean isIndent) throws Exception {
		if(isIndent) {
			printi();
		}
		append(that.getOp().toString());
		that.getOperand().accept(this, false);

		return null;
	}

	public String visit(StmtBlock that, Boolean isIndent) throws Exception {
		printi("\n");
		printi("{");
		printi("\n");
		enter();
		for (Statement stmt : that.getStmts()) {
			stmt.accept(this, true);
		}
		leave();
		printi("\n");
		printi("}");
		printi("\n");
		return null;
	}


	public String visit(AssignStmt that, Boolean isIndent) throws Exception {

		that.getDest().accept(this, isIndent);
		append(" = ");
		that.getSrc().accept(this, false);
		append(";\n");


		return null;
	}

	public String visit(IfStmt that, Boolean isIndent) throws Exception {

		printi("if (");
		that.getCondition().accept(this, false);
		append(") ");

		// regular if
		if(that.getIfStmt() instanceof StmtBlock) {
			that.getIfStmt().accept(this, true);
		} 
		// if(a) return true;
		else { 
			append("\n");
			enter();
			that.getIfStmt().accept(this, true);
			leave();
		}

		if (that.getElseStmt() != null) {
			printi("else ");


//			regular else
			if(that.getElseStmt() instanceof StmtBlock) {
				that.getElseStmt().accept(this, true);
			} 
			// else return true;
			else { 
				append("\n");
				enter();
				that.getElseStmt().accept(this, true);
				leave();
			}
		} else {

		}

		return null;
	}

	public String visit(ReturnStmt that, Boolean isIndent) throws Exception {
		if(isIndent) {
			printi();
		}

		append("return ");
		if (that.getExpr() != null) {
			that.getExpr().accept(this, false);
		} else {

		}
		append(";\n");

		return null;
	}

	public String visit(VarDeclStmt that, Boolean isIndent) throws Exception {

		that.getType().accept(this, true);
		append(that.getVar().getID());

		if (that.getInit() != null) {
			append(" = ");
			that.getInit().accept(this, false);
		} else {

		}
		append(";\n");
		return null;
	}

	public String visit(WhileStmt that, Boolean isIndent) throws Exception {

		printi("while(");
		that.getCondition().accept(this, false);

		if (that.getBody() instanceof StmtBlock) {
			append(")");
			that.getBody().accept(this, true);

		} else {
			append(") \n");
			enter();

			that.getBody().accept(this, true);
			leave();
		}

		return null;
	}

	/** break, continue */
	public String visit(BreakStmt that, Boolean isIndent) throws Exception {
		if(isIndent) {
			printi();
		}
		append(" break;\n");
		return null;
	}

	public String visit(ContinueStmt that, Boolean isIndent) throws Exception {
		if(isIndent) {
			printi();
		}
		append(" continue;\n");
		return null;
	}


	/** literals */
	public String visit(BoolLiteral that, Boolean isIndent) throws Exception {

		append(that.getValue()+"");
		return null;
	}

	public String visit(IntLiteral that, Boolean isIndent) throws Exception {

		append(that.getValue() + "");
		return null;
	}

	public String visit(NullLiteral that, Boolean isIndent) throws Exception {

		append("null");
		return null;
	}

	public String visit(StringLiteral that, Boolean isIndent) throws Exception {

		append("\""
				+ that.getValue().toString() // print escaped string
				.replace("\\", "\\\\").replace("\"", "\\\"").replace(
						"\n", "\\n").replace("\t", "\\t") + "\"");
		return null;
	}
}
